/* -------------------------------------------------------------------------
OpenTripPlanner GWT Client
Copyright (C) 2015 Mecatran - info@mecatran.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
------------------------------------------------------------------------- */
package com.mecatran.otp.gwt.client.view;
import java.util.Date;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.Context2d.LineCap;
import com.google.gwt.canvas.dom.client.CssColor;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.mecatran.otp.gwt.client.PlannerResources;
import com.mecatran.otp.gwt.client.i18n.I18nUtils;
import com.mecatran.otp.gwt.client.model.AlertBean;
import com.mecatran.otp.gwt.client.model.ItineraryBean;
import com.mecatran.otp.gwt.client.model.ItineraryLegBean;
import com.mecatran.otp.gwt.client.model.ItineraryRoadLegBean;
import com.mecatran.otp.gwt.client.model.ItineraryRoadStepBean;
import com.mecatran.otp.gwt.client.model.ItineraryTransitLegBean;
import com.mecatran.otp.gwt.client.model.TransitRouteBean;
import com.mecatran.otp.gwt.client.model.TransportMode;
import com.mecatran.otp.gwt.client.model.Wgs84LatLonBean;
import com.mecatran.otp.gwt.client.utils.FormatUtils;
import com.mecatran.otp.gwt.client.view.ItineraryWidget.ItineraryListener;
public class ItineraryDetailsWidget extends Composite {
private ItineraryListener listener;
private boolean selected;
public ItineraryDetailsWidget(ItineraryBean itinerary,
ItineraryListener aListener) {
this.listener = aListener;
VerticalPanel rootPanel = new VerticalPanel();
/* Alerts on top */
for (AlertBean alert : itinerary.getAlerts()) {
AlertWidget alertWidget = new AlertWidget(alert);
alertWidget.setStyleName("itinerary-alert");
rootPanel.add(alertWidget);
}
/* Departure */
rootPanel.add(getEndPointDetails("departure",
itinerary.getDepartureTime(), itinerary.getStartAddress(),
itinerary.getStartLocation()));
/* Details */
for (ItineraryLegBean leg : itinerary.getLegs()) {
switch (leg.getTravelType()) {
case ROAD:
rootPanel.add(getRoadDetails(leg.getAsRoadLeg()));
break;
case TRANSIT:
rootPanel.add(getTransitDetails(leg.getAsTransitLeg()));
break;
default:
throw new IllegalArgumentException("Unsupported leg type: "
+ leg.getTravelType());
}
}
/* Arrival */
rootPanel.add(getEndPointDetails("arrival", itinerary.getArrivalTime(),
itinerary.getEndAddress(), itinerary.getEndLocation()));
/* Copyrights */
Label copyrightLabel = new Label(itinerary.getCopyrights());
copyrightLabel.addStyleName("itinerary-copyrights");
rootPanel.add(copyrightLabel);
initWidget(rootPanel);
}
public void setSelected(boolean selected) {
this.selected = selected;
}
private Widget getEndPointDetails(final String css, Date time,
String address, final Wgs84LatLonBean position) {
HTML line = new HTML();
line.addStyleName("itinerary-details-line");
line.addStyleName("itinerary-details-" + css);
line.addStyleName(css + "-icon");
String html = "<span class='time'>" + FormatUtils.formatTime(time)
+ "</span> - " + FormatUtils.formatAddress(address);
line.setHTML(html);
final String infoHtml = "<div class='info-panel-" + css + " " + css
+ "-icon'>" + html + "</div>";
line.addMouseOverHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
if (selected)
listener.onItineraryStepClicked(infoHtml, position);
}
});
return line;
}
private Widget getRoadDetails(ItineraryRoadLegBean leg) {
VerticalPanel rootPanel = new VerticalPanel();
rootPanel.addStyleName("itinerary-details-line");
styleComponentWithMode(rootPanel, leg.getMode(), null);
// Header: main instruction
// TODO Use templating
HTML instructionsLabel = new HTML(leg.getInstructions());
instructionsLabel.addStyleName("itinerary-details-road-first-line");
rootPanel.add(instructionsLabel);
final String mainRoadHtml = "<div class='info-panel-road "
+ FormatUtils.getCssClassNameFromTransportMode(leg.getMode())
+ "-icon'>" + leg.getInstructions() + "</div>";
leg.setCustomHtmlDetails(mainRoadHtml);
final Wgs84LatLonBean mainRoadPosition = leg.getStartLocation();
instructionsLabel.addMouseOverHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
if (selected)
listener.onItineraryStepClicked(mainRoadHtml,
mainRoadPosition);
}
});
// Duration and distance + details link
FlowPanel distanceAndLinkPanel = new FlowPanel();
rootPanel.add(distanceAndLinkPanel);
HTML distanceDurationLabel = new HTML(FormatUtils.formatDistance(leg
.getDistanceMeters())
+ " - "
+ FormatUtils.formatDuration(leg.getDurationSeconds()));
distanceDurationLabel
.addStyleName("itinerary-details-road-second-line");
distanceAndLinkPanel.add(distanceDurationLabel);
// Open/close details link
final Anchor showHideDetailsLink = new Anchor(
I18nUtils.tr("show.road.details"));
distanceAndLinkPanel.add(showHideDetailsLink);
showHideDetailsLink.addStyleName("itinerary-details-show-hide");
// Details
final VerticalPanel detailsPanel = new VerticalPanel();
rootPanel.add(detailsPanel);
detailsPanel.setVisible(false);
int nDetails = 0;
for (ItineraryRoadStepBean step : leg.getRoadSteps()) {
if (step.getInstructions() == null) {
// Can happen in case of transfers between two transit legs
continue;
}
// TODO Use templating
String html = step.getInstructions()
+ (step.getDistanceMeters() >= 5 ? " <span class='distance'>("
+ FormatUtils.formatDistance(step
.getDistanceMeters())
+ " - "
+ FormatUtils.formatDuration(step
.getDurationSeconds()) + ")</span>" : "");
final String infoHtml = "<div class='info-panel-road-step'>" + html
+ "</div>";
final Wgs84LatLonBean startLocation = step.getStartLocation();
HTML stepLabel = new HTML(html);
stepLabel.addStyleName("itinerary-details-road-step");
stepLabel.addMouseOverHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
if (selected)
listener.onItineraryStepClicked(infoHtml, startLocation);
}
});
detailsPanel.add(stepLabel);
nDetails++;
}
// Open/close behavior
showHideDetailsLink.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (detailsPanel.isVisible()) {
showHideDetailsLink.setText(I18nUtils
.tr("show.road.details"));
detailsPanel.setVisible(false);
} else {
showHideDetailsLink.setText(I18nUtils
.tr("hide.road.details"));
detailsPanel.setVisible(true);
}
event.stopPropagation();
}
});
// Can happen: no detail instructions.
if (nDetails == 0)
showHideDetailsLink.setVisible(false);
return rootPanel;
}
private Widget getTransitDetails(ItineraryTransitLegBean leg) {
VerticalPanel line = new VerticalPanel();
TransitRouteBean route = leg.getRoute();
line.addStyleName("itinerary-details-line");
styleComponentWithMode(line, leg.getMode(), route.getBackgroundColor());
// line.addStyleName(ItineraryWidget.getCssClassNameFromTransportMode(leg
// .getMode()) + "-icon");
// First line: time - departure stop name
HTML departureLine = new HTML();
departureLine.addStyleName("itinerary-details-transit-departure");
departureLine.setHTML("<span class='time'>"
+ FormatUtils.formatTime(leg.getDepartureTime()) + "</span> "
+ leg.getDepartureStop().getName());
String codeHtml = "<span class='route-code' style='color:"
+ route.getForegroundColor() + "; background-color:"
+ route.getBackgroundColor() + "'>" + route.getCode()
+ "</span>";
String transitHtml = "<div class='info-panel-transit "
+ FormatUtils.getCssClassNameFromTransportMode(leg.getMode())
+ "-icon'>";
final String departureHtml = transitHtml + "<span class='time'>"
+ FormatUtils.formatTime(leg.getDepartureTime())
+ "</span> - <span class='stop'>"
+ leg.getDepartureStop().getName() + "</span><br/>" + codeHtml
+ " " + (route.getName() != null ? route.getName() : "")
+ "<br/>" + I18nUtils.tr("heading.to")
+ " <span class='headsign'>" + leg.getHeadsign()
+ "</span></div>";
final Wgs84LatLonBean departureLocation = leg.getDepartureStop()
.getLocation();
leg.setCustomHtmlDetails(departureHtml);
departureLine.addMouseOverHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
if (selected)
listener.onItineraryStepClicked(departureHtml,
departureLocation);
}
});
line.add(departureLine);
// Second line: Route code and headsign
HTML headsignLine = new HTML();
headsignLine.addStyleName("itinerary-details-transit-headsign");
headsignLine.setHTML(codeHtml + " " + I18nUtils.tr("heading.to") + " "
+ leg.getHeadsign());
line.add(headsignLine);
// Third line: time - arrival stop name
HTML arrivalLine = new HTML();
arrivalLine.addStyleName("itinerary-details-transit-arrival");
arrivalLine.setHTML("<span class='time'>"
+ FormatUtils.formatTime(leg.getArrivalTime()) + "</span> "
+ leg.getArrivalStop().getName());
final String arrivalHtml = transitHtml + "<span class='time'>"
+ FormatUtils.formatTime(leg.getArrivalTime())
+ "</span> - <small>" + I18nUtils.tr("hop.off.at")
+ "</small> <span class='stop'>"
+ leg.getArrivalStop().getName() + "</span></div>";
final Wgs84LatLonBean arrivalLocation = leg.getArrivalStop()
.getLocation();
arrivalLine.addMouseOverHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
if (selected)
listener.onItineraryStepClicked(arrivalHtml,
arrivalLocation);
}
});
line.add(arrivalLine);
return line;
}
/**
* Build the background image for the widget, according to the mode. Draw
* the mode image and a solid line below it with the route color (if in
* transit mode) or a dotted line (if in road mode). Set the
* background-image to the generated image for the given widget.
*/
public static void styleComponentWithMode(final Widget widget,
TransportMode mode, String color) {
PlannerResources resources = PlannerResources.INSTANCE;
ImageResource baseImage = null;
boolean road = false;
switch (mode) {
case WALK:
road = true;
color = "#666666";
baseImage = resources.modeWalkPng();
break;
case BICYCLE:
road = true;
color = "#23C30B";
baseImage = resources.modeBicyclePng();
break;
case BICYCLE_RENTAL:
road = true;
color = "#23C30B";
baseImage = resources.modeBikeRentalPng();
break;
case CAR:
road = true;
color = "#333333";
baseImage = resources.modeCarPng();
break;
default:
case BUS:
baseImage = resources.modeBusPng();
break;
case TRAM:
baseImage = resources.modeTramPng();
break;
case FERRY:
baseImage = resources.modeFerryPng();
break;
case GONDOLA:
baseImage = resources.modeGondolaPng();
break;
case PLANE:
baseImage = resources.modePlanePng();
break;
case RAIL:
baseImage = resources.modeRailPng();
break;
case SUBWAY:
baseImage = resources.modeSubwayPng();
break;
case TROLLEY:
baseImage = resources.modeTrolleyPng();
break;
}
final String url = baseImage.getSafeUri().asString();
final Canvas canvas = Canvas.createIfSupported();
if (canvas != null) {
int width = baseImage.getWidth();
int height = 1000;
canvas.setCoordinateSpaceWidth(width);
canvas.setCoordinateSpaceHeight(height);
final Context2d context = canvas.getContext2d();
context.setLineCap(LineCap.BUTT);
if (road) {
context.setStrokeStyle(CssColor.make(color));
context.setLineWidth(4);
for (int y = baseImage.getHeight(); y < 1000; y += 7) {
context.moveTo(width / 2, y);
context.lineTo(width / 2, y + 5);
}
context.stroke();
} else {
context.setStrokeStyle(CssColor.make("#000000"));
context.setLineWidth(5);
context.moveTo(width / 2, 0);
context.lineTo(width / 2, height - 1);
context.stroke();
context.setStrokeStyle(CssColor.make(color));
context.setLineWidth(4);
context.moveTo(width / 2, 0);
context.lineTo(width / 2, height - 1);
context.stroke();
}
/*
* HACK ALERT! Image.onLoad event does not fire up when using
* internal resources (URL is internal data), but using the image
* immediately does not work (image does not seems to be ready). We
* defer the processing of the image rendering to a timer delayed a
* bit.
*/
Timer timer = new Timer() {
@Override
public void run() {
Image image = new Image(url);
ImageElement e = ImageElement.as(image.getElement());
context.drawImage(e, 0, 0);
String url2 = canvas.toDataUrl("image/png");
widget.getElement().getStyle()
.setBackgroundImage("url('" + url2 + "')");
}
};
timer.schedule(500);
} else {
widget.getElement().getStyle()
.setBackgroundImage("url('" + url + "')");
}
}
}